package org.threads.parallelseries;

/**
 * Stands for calculating summ of sin-cos series in the
 * interval [-N; N]. Calculation is performed in parallel
 * form.
 */
public class SinCos {

    private static final double MIN_STEP = 0.000000001;
    private static double step = 0.00001;
    private static volatile double result;

    private static final String ZERO_N_EX = "N can't be zero!";
    private static final String WRONG_STEP_EX = "Wrong step!";

    private double N;
    private int threadCount;


    /**
     * Thread implementation for parallel calculation.
     * Each of interval parts is calculated in different thread.
     */
    private static class SinCosCalculator extends Thread {

        private double from;
        private double to;

        /**
         * The main constructor of the thread, which defines
         * the range where custom part of series is calculated.
         *
         * @param from left brink
         * @param to right brink
         */
        public SinCosCalculator(double from, double to) {

            this.from = from;
            this.to = to;
        }

        /**
         * Returns the value of function sin(x)*cos(x)
         * for defined x argument.
         *
         * @param x function's argument
         * @return function's value
         */
        private final double sinCosFunc(double x) {
            return Math.sin(x) * Math.cos(x);
        }

        @Override
        public void run() {

            double localResult = 0.0;

            for (double x = from; x < to; x += step) {

                double a = sinCosFunc(x) + sinCosFunc(-x);
                localResult += a;
            }

            result += localResult;
        }

    }

    /**
     * Creates an instance of <code>SinCos</code>.
     *
     * @param threadCount threads' count is used for calculation
     * @param N defines the range for calculation
     * @param step accuracy
     */
    public SinCos(int threadCount, double N, double step) {

        setThreadCount(threadCount);
        setN(N);
        setStep(step);
    }

    /**
     * Call this method to calculate the series.
     *
     * @return the series' summ
     */
    public double calculate() {

        result = 0.0;

        Thread[] calcThreads = new SinCosCalculator[threadCount];
        final double diff = (2 * N) / threadCount;
        double from;
        double to;

        for (int i = 0; i < threadCount; i++) {
            from = diff * i;
            to = -N + diff * (i + 1);
            calcThreads[i] = new SinCosCalculator(from, to);
        }
        System.out.println();

        long start = System.currentTimeMillis();
        for (Thread item : calcThreads) {
            item.start();
        }

        for (Thread item : calcThreads) {
            try {
                item.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long dt = System.currentTimeMillis() - start;
        System.out.println("[ThreadCount = " + threadCount + "; dt = " + dt + " ms]");

        return result;
    }

    public void setN(double N) {

        if (N == 0) {
            throw new IllegalArgumentException(ZERO_N_EX);
        }
        this.N = N;
    }

    public void setStep(double step) {

        if (step < MIN_STEP) {
            throw new IllegalArgumentException(WRONG_STEP_EX);
        }
        this.step = step;
    }

    public void setThreadCount(int threadCount) {

        if (threadCount < 1) {
            threadCount = 1;
        }
        this.threadCount = threadCount;
    }

    /**
     * Entry point, used for testing.
     *
     * @param args
     */
    public static void main(String[] args) {

        final int SINCOS_THREADS_MIN = 1;
        final int SINCOS_THREADS_MAX = 5;
        final double N = 1000;
        final double PRECISION = 0.0001f;

        SinCos sinCos;
        double res;

        for (int i = SINCOS_THREADS_MIN; i <= SINCOS_THREADS_MAX; i++) {
            sinCos = new SinCos(i, N, PRECISION);
            res = sinCos.calculate();

            System.out.println("The result is " + res);
            System.out.println();
        }


    }
}
